import * as React from 'react';
import { spy } from 'sinon';
import {
  adapter,
  createSchedulerRenderer,
  EventBuilder,
  SchedulerStoreRunner,
  StateWatcher,
  StoreSpy,
} from 'test/utils/scheduler';

import { screen, within } from '@mui/internal-test-utils';
import {
  SchedulerResource,
  SchedulerResourceId,
  SchedulerOccurrencePlaceholderCreation,
} from '@mui/x-scheduler-headless/models';
import { Popover } from '@base-ui-components/react/popover';
import { EventCalendarStoreContext } from '@mui/x-scheduler-headless/use-event-calendar-store-context';
import { EventCalendarProvider } from '@mui/x-scheduler-headless/event-calendar-provider';
import { SchedulerEvent } from '@mui/x-scheduler/models';
import { EventPopoverContent } from './EventPopover';
import { RecurringScopeDialog } from '../scope-dialog/ScopeDialog';

const DEFAULT_EVENT: SchedulerEvent = EventBuilder.new()
  .title('Running')
  .description('Morning run')
  .singleDay('2025-05-26T07:30:00', 45)
  .resource('r2')
  .build();

const resources: SchedulerResource[] = [
  {
    id: 'r1',
    title: 'Work',
    eventColor: 'blue',
  },
  {
    id: 'r2',
    title: 'Personal',
    eventColor: 'cyan',
  },
];

describe('<EventPopoverContent />', () => {
  const anchor = document.createElement('button');
  document.body.appendChild(anchor);

  const defaultProps = {
    anchor,
    container: document.body,
    occurrence: EventBuilder.new()
      .id(DEFAULT_EVENT.id)
      .title(DEFAULT_EVENT.title)
      .description(DEFAULT_EVENT.description)
      .span(DEFAULT_EVENT.start, DEFAULT_EVENT.end)
      .resource(DEFAULT_EVENT.resource as SchedulerResourceId)
      .toOccurrence(),
    onClose: () => {},
  };

  const { render } = createSchedulerRenderer();

  it('should render the event data in the form fields', async () => {
    const { user } = render(
      <EventCalendarProvider events={[DEFAULT_EVENT]} resources={resources}>
        <Popover.Root open>
          <EventPopoverContent {...defaultProps} />
        </Popover.Root>
      </EventCalendarProvider>,
    );
    expect(screen.getByDisplayValue(DEFAULT_EVENT.title)).not.to.equal(null);
    expect(screen.getByDisplayValue(DEFAULT_EVENT.description ?? '')).not.to.equal(null);
    expect(screen.getByLabelText(/start date/i)).to.have.value('2025-05-26');
    expect(screen.getByLabelText(/end date/i)).to.have.value('2025-05-26');
    expect(screen.getByLabelText(/start time/i)).to.have.value('07:30');
    expect(screen.getByLabelText(/end time/i)).to.have.value('08:15');
    expect(screen.getByRole('checkbox', { name: /all day/i })).to.have.attribute(
      'aria-checked',
      'false',
    );
    expect(screen.getByRole('button', { name: /resource/i }).textContent).to.match(/personal/i);
    await user.click(screen.getByRole('tab', { name: /recurrence/i }));
    expect(screen.getByRole('combobox', { name: /recurrence/i }).textContent).to.match(
      /don't repeat/i,
    );
  });

  it('should call "onEventsChange" with updated values on submit', async () => {
    const onEventsChange = spy();
    const { user } = render(
      <EventCalendarProvider
        events={[DEFAULT_EVENT]}
        onEventsChange={onEventsChange}
        resources={resources}
      >
        <Popover.Root open>
          <EventPopoverContent {...defaultProps} />
        </Popover.Root>
      </EventCalendarProvider>,
    );
    await user.type(screen.getByLabelText(/event title/i), ' test');
    await user.click(screen.getByRole('checkbox', { name: /all day/i }));
    await user.click(screen.getByRole('tab', { name: /recurrence/i }));
    await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
    await user.click(await screen.findByRole('option', { name: /repeats daily/i }));
    await user.click(screen.getByRole('button', { name: /resource/i }));
    await user.click(await screen.findByRole('menuitemradio', { name: /work/i }));
    await user.click(await screen.findByRole('menuitemradio', { name: /pink/i }));
    await user.click(screen.getByRole('button', { name: /save changes/i }));

    expect(onEventsChange.calledOnce).to.equal(true);
    const updated = onEventsChange.firstCall.firstArg[0];

    const expectedUpdatedEvent = {
      id: DEFAULT_EVENT.id,
      title: 'Running test',
      description: DEFAULT_EVENT.description,
      start: adapter.startOfDay(DEFAULT_EVENT.start),
      end: adapter.endOfDay(DEFAULT_EVENT.end),
      allDay: true,
      rrule: { freq: 'DAILY', interval: 1 },
      resource: 'r1',
      color: 'pink',
    };

    expect(updated).to.deep.equal(expectedUpdatedEvent);
  });

  it('should show error if start date is after end date', async () => {
    const { user } = render(
      <EventCalendarProvider events={[DEFAULT_EVENT]}>
        <Popover.Root open>
          <EventPopoverContent {...defaultProps} />
        </Popover.Root>
      </EventCalendarProvider>,
    );
    await user.clear(screen.getByLabelText(/start date/i));
    await user.type(screen.getByLabelText(/start date/i), '2025-05-27');
    await user.clear(screen.getByLabelText(/end date/i));
    await user.type(screen.getByLabelText(/end date/i), '2025-05-26');
    await user.click(screen.getByRole('button', { name: /save changes/i }));

    expect(screen.getDescriptionOf(screen.getByLabelText(/start date/i)).textContent).to.match(
      /start.*before.*end/i,
    );
  });

  it('should call "onEventsChange" with the updated values when delete button is clicked', async () => {
    const onEventsChange = spy();
    const { user } = render(
      <EventCalendarProvider events={[DEFAULT_EVENT]} onEventsChange={onEventsChange}>
        <Popover.Root open>
          <EventPopoverContent {...defaultProps} />
        </Popover.Root>
      </EventCalendarProvider>,
    );
    await user.click(screen.getByRole('button', { name: /delete event/i }));
    expect(onEventsChange.calledOnce).to.equal(true);
    expect(onEventsChange.firstCall.firstArg).to.deep.equal([]);
  });

  it('should handle read-only events and render ReadonlyContent', () => {
    const readOnlyEvent = { ...DEFAULT_EVENT, readOnly: true };

    const readOnlyOccurrence = EventBuilder.new(adapter)
      .id(readOnlyEvent.id)
      .title(readOnlyEvent.title)
      .description(readOnlyEvent.description)
      .span(readOnlyEvent.start, readOnlyEvent.end)
      .readOnly(true)
      .toOccurrence();

    render(
      <EventCalendarProvider events={[readOnlyEvent]} resources={resources}>
        <Popover.Root open>
          <EventPopoverContent {...defaultProps} occurrence={readOnlyOccurrence} />
        </Popover.Root>
      </EventCalendarProvider>,
    );
    // Should display title as text, not in an input
    expect(screen.getByText(DEFAULT_EVENT.title)).not.to.equal(null);
    expect(screen.queryByLabelText(/event title/i)).to.equal(null);

    // Should display description as text, not in an input
    expect(screen.getByText(DEFAULT_EVENT.description ?? '')).not.to.equal(null);
    expect(screen.queryByLabelText(/description/i)).to.equal(null);

    // Should not have date/time inputs
    expect(screen.queryByLabelText(/start date/i)).to.equal(null);
    expect(screen.queryByLabelText(/end date/i)).to.equal(null);
    expect(screen.queryByLabelText(/start time/i)).to.equal(null);
    expect(screen.queryByLabelText(/end time/i)).to.equal(null);

    // Should not have all-day checkbox
    expect(screen.queryByRole('checkbox', { name: /all day/i })).to.equal(null);

    // Should not have resource/recurrence comboboxes
    expect(screen.queryByRole('button', { name: /resource/i })).to.equal(null);
    expect(screen.queryByRole('combobox', { name: /recurrence/i })).to.equal(null);
  });

  it('should handle read-only events if EventCalendar is read-only', () => {
    const readOnlyOccurrence = EventBuilder.new(adapter)
      .id(DEFAULT_EVENT.id)
      .title(DEFAULT_EVENT.title)
      .description(DEFAULT_EVENT.description)
      .span(DEFAULT_EVENT.start, DEFAULT_EVENT.end)
      .readOnly(true)
      .toOccurrence();

    render(
      <EventCalendarProvider events={[DEFAULT_EVENT]} resources={resources} readOnly>
        <Popover.Root open>
          <EventPopoverContent {...defaultProps} occurrence={readOnlyOccurrence} />
        </Popover.Root>
      </EventCalendarProvider>,
    );
    // Should display title as text, not in an input
    expect(screen.getByText(DEFAULT_EVENT.title)).not.to.equal(null);
    expect(screen.queryByLabelText(/event title/i)).to.equal(null);

    // Should display description as text, not in an input
    expect(screen.getByText(DEFAULT_EVENT.description ?? '')).not.to.equal(null);
    expect(screen.queryByLabelText(/description/i)).to.equal(null);

    // Should not have date/time inputs
    expect(screen.queryByLabelText(/start date/i)).to.equal(null);
    expect(screen.queryByLabelText(/end date/i)).to.equal(null);
    expect(screen.queryByLabelText(/start time/i)).to.equal(null);
    expect(screen.queryByLabelText(/end time/i)).to.equal(null);

    // Should not have all-day checkbox
    expect(screen.queryByRole('checkbox', { name: /all day/i })).to.equal(null);

    // Should not have resource/recurrence comboboxes
    expect(screen.queryByRole('button', { name: /resource/i })).to.equal(null);
    expect(screen.queryByRole('combobox', { name: /recurrence/i })).to.equal(null);
  });

  it('should handle a resource without an eventColor (fallback to default)', async () => {
    const onEventsChange = spy();

    const resourcesNoColor: SchedulerResource[] = [
      { id: 'r1', title: 'Work', eventColor: 'blue' },
      { id: 'r2', title: 'Personal', eventColor: 'cyan' },
      { id: 'r3', title: 'NoColor' },
    ];

    const eventWithNoResourceColor: SchedulerEvent = {
      ...DEFAULT_EVENT,
      resource: 'r3',
    };

    const eventWithNoResourceColorOccurrence = EventBuilder.new(adapter)
      .id(eventWithNoResourceColor.id)
      .title(eventWithNoResourceColor.title)
      .description(eventWithNoResourceColor.description)
      .span(eventWithNoResourceColor.start, eventWithNoResourceColor.end)
      .resource(eventWithNoResourceColor.resource as SchedulerResourceId)
      .toOccurrence();

    render(
      <EventCalendarProvider
        events={[eventWithNoResourceColor]}
        onEventsChange={onEventsChange}
        resources={resourcesNoColor}
      >
        <Popover.Root open>
          <EventPopoverContent {...defaultProps} occurrence={eventWithNoResourceColorOccurrence} />
        </Popover.Root>
      </EventCalendarProvider>,
    );

    expect(screen.getByRole('button', { name: /resource/i }).textContent).to.match(/NoColor/i);
    expect(document.querySelector('.ResourceLegendColor')).to.have.class('palette-jade');
  });

  it('should fallback to "No resource" with default color when the event has no resource', async () => {
    const onEventsChange = spy();

    const eventWithoutResource: SchedulerEvent = {
      ...DEFAULT_EVENT,
      resource: undefined,
    };

    const eventWithoutResourceOccurrence = EventBuilder.new(adapter)
      .id(eventWithoutResource.id)
      .title(eventWithoutResource.title)
      .description(eventWithoutResource.description)
      .span(eventWithoutResource.start, eventWithoutResource.end)
      .toOccurrence();

    const { user } = render(
      <EventCalendarProvider
        events={[eventWithoutResource]}
        onEventsChange={onEventsChange}
        resources={resources}
      >
        <Popover.Root open>
          <EventPopoverContent {...defaultProps} occurrence={eventWithoutResourceOccurrence} />
        </Popover.Root>
      </EventCalendarProvider>,
    );

    expect(screen.getByRole('button', { name: /resource/i }).textContent).to.match(/no resource/i);

    expect(document.querySelector('.ResourceLegendColor')).to.have.class('palette-jade');

    await user.click(screen.getByRole('button', { name: /save changes/i }));

    expect(onEventsChange.calledOnce).to.equal(true);
    const updated = onEventsChange.firstCall.firstArg[0];
    expect(updated.resource).to.equal(undefined);
  });

  describe('Event creation', () => {
    it('should change surface of the placeholder to day-grid when all-day is changed to true', async () => {
      const start = adapter.date('2025-05-26T07:30:00', 'default');
      const end = adapter.date('2025-05-26T08:30:00', 'default');
      const handleSurfaceChange = spy();

      const creationOccurrence = EventBuilder.new(adapter)
        .id('tmp')
        .span(start, end)
        .toOccurrence();

      const { user } = render(
        <EventCalendarProvider events={[]} resources={resources}>
          <SchedulerStoreRunner
            context={EventCalendarStoreContext}
            onMount={(store) =>
              store.setOccurrencePlaceholder({
                type: 'creation',
                surfaceType: 'time-grid',
                start,
                end,
                lockSurfaceType: false,
                resourceId: null,
              })
            }
          />
          <Popover.Root open>
            <EventPopoverContent {...defaultProps} occurrence={creationOccurrence} />
          </Popover.Root>
          <StateWatcher
            Context={EventCalendarStoreContext}
            selector={(s) => s.occurrencePlaceholder?.surfaceType}
            onValueChange={handleSurfaceChange}
          />
        </EventCalendarProvider>,
      );

      expect(handleSurfaceChange.lastCall?.firstArg).to.equal('time-grid');

      await user.click(screen.getByRole('checkbox', { name: /all day/i }));

      expect(handleSurfaceChange.lastCall?.firstArg).to.equal('day-grid');
    });

    it('should change surface of the placeholder to time-grid when all-day is changed to false', async () => {
      const start = adapter.date('2025-05-26T07:30:00', 'default');
      const end = adapter.date('2025-05-26T08:30:00', 'default');
      const handleSurfaceChange = spy();

      const creationOccurrence = EventBuilder.new(adapter)
        .id('tmp')
        .span(start, end)
        .allDay(true)
        .toOccurrence();

      const { user } = render(
        <EventCalendarProvider events={[]} resources={resources}>
          <SchedulerStoreRunner
            context={EventCalendarStoreContext}
            onMount={(store) =>
              store.setOccurrencePlaceholder({
                type: 'creation',
                surfaceType: 'day-grid',
                start,
                end,
                lockSurfaceType: false,
                resourceId: null,
              })
            }
          />
          <Popover.Root open>
            <EventPopoverContent {...defaultProps} occurrence={creationOccurrence} />
          </Popover.Root>
          <StateWatcher
            Context={EventCalendarStoreContext}
            selector={(s) => s.occurrencePlaceholder?.surfaceType}
            onValueChange={handleSurfaceChange}
          />
        </EventCalendarProvider>,
      );

      expect(handleSurfaceChange.lastCall?.firstArg).to.equal('day-grid');

      await user.click(screen.getByRole('checkbox', { name: /all day/i }));

      expect(handleSurfaceChange.lastCall?.firstArg).to.equal('time-grid');
    });

    it('should not change surfaceType when all day changed to true and lockSurfaceType=true', async () => {
      const start = adapter.date('2025-05-26T07:30:00', 'default');
      const end = adapter.date('2025-05-26T08:30:00', 'default');
      const handleSurfaceChange = spy();

      const creationOccurrence = EventBuilder.new(adapter)
        .id('tmp')
        .span(start, end)
        .toOccurrence();

      const { user } = render(
        <EventCalendarProvider events={[]} resources={resources}>
          <SchedulerStoreRunner
            context={EventCalendarStoreContext}
            onMount={(store) =>
              store.setOccurrencePlaceholder({
                type: 'creation',
                surfaceType: 'time-grid',
                start,
                end,
                lockSurfaceType: true,
                resourceId: null,
              })
            }
          />
          <Popover.Root open>
            <EventPopoverContent {...defaultProps} occurrence={creationOccurrence as any} />
          </Popover.Root>
          <StateWatcher
            Context={EventCalendarStoreContext}
            selector={(s) => s.occurrencePlaceholder?.surfaceType}
            onValueChange={handleSurfaceChange}
          />
        </EventCalendarProvider>,
      );
      expect(handleSurfaceChange.lastCall?.firstArg).to.equal('time-grid');

      await user.click(screen.getByRole('checkbox', { name: /all day/i }));

      expect(handleSurfaceChange.lastCall?.firstArg).to.equal('time-grid');
    });

    it('should call createEvent with metaChanges + computed start/end on Submit', async () => {
      const start = adapter.date('2025-06-10T09:00:00', 'default');
      const end = adapter.date('2025-06-10T09:30:00', 'default');
      const placeholder: SchedulerOccurrencePlaceholderCreation = {
        type: 'creation',
        surfaceType: 'time-grid' as const,
        start,
        end,
        lockSurfaceType: false,
        resourceId: null,
      };

      const creationOccurrence = EventBuilder.new(adapter)
        .id('placeholder-id')
        .span(start, end)
        .title('')
        .description('')
        .toOccurrence();

      const onEventsChange = spy();
      let createEventSpy;

      const { user } = render(
        <EventCalendarProvider events={[]} resources={resources} onEventsChange={onEventsChange}>
          <SchedulerStoreRunner
            context={EventCalendarStoreContext}
            onMount={(store) => store.setOccurrencePlaceholder(placeholder)}
          />
          <StoreSpy
            Context={EventCalendarStoreContext}
            method="createEvent"
            onSpyReady={(sp) => {
              createEventSpy = sp;
            }}
          />

          <Popover.Root open>
            <EventPopoverContent {...defaultProps} occurrence={creationOccurrence} />
          </Popover.Root>
        </EventCalendarProvider>,
      );

      await user.type(screen.getByLabelText(/event title/i), ' New title ');
      await user.type(screen.getByLabelText(/description/i), ' Some details ');
      await user.click(screen.getByRole('button', { name: /resource/i }));
      await user.click(await screen.findByRole('menuitemradio', { name: /work/i }));
      await user.click(screen.getByRole('tab', { name: /recurrence/i }));
      await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
      await user.click(await screen.findByRole('option', { name: /daily/i }));
      await user.click(screen.getByRole('button', { name: /save changes/i }));

      expect(createEventSpy?.calledOnce).to.equal(true);
      const payload = createEventSpy.lastCall.firstArg;

      expect(payload.title).to.equal('New title');
      expect(payload.description).to.equal('Some details');
      expect(payload.allDay).to.equal(false);
      expect(payload.resource).to.equal('r1');
      expect(payload.start).toEqualDateTime(start);
      expect(payload.end).toEqualDateTime(end);
      expect(payload.rrule).to.deep.equal({ freq: 'DAILY', interval: 1 });
    });
  });
  describe('Event editing', () => {
    describe('Recurring events', () => {
      const originalRecurringEvent = EventBuilder.new()
        .title('Daily standup')
        .description('sync')
        .singleDay('2025-06-11T10:00:00', 30)
        .resource('r2')
        .recurrent('DAILY')
        .build();
      const originalRecurringEventOccurrence = EventBuilder.new(adapter)
        .id(originalRecurringEvent.id)
        .title(originalRecurringEvent.title)
        .description(originalRecurringEvent.description)
        .span(originalRecurringEvent.start, originalRecurringEvent.end)
        .recurrent('DAILY')
        .toOccurrence();

      it('should not call updateRecurringEvent if the user cancels the scope dialog', async () => {
        let updateRecurringEventSpy, selectRecurringEventUpdateScopeSpy;
        const containerRef = React.createRef<HTMLDivElement>();

        const { user } = render(
          <React.Fragment>
            <div ref={containerRef} />
            <EventCalendarProvider events={[originalRecurringEvent]} resources={resources}>
              <StoreSpy
                Context={EventCalendarStoreContext}
                method="updateRecurringEvent"
                onSpyReady={(sp) => {
                  updateRecurringEventSpy = sp;
                }}
              />
              <StoreSpy
                Context={EventCalendarStoreContext}
                method="selectRecurringEventUpdateScope"
                onSpyReady={(sp) => {
                  selectRecurringEventUpdateScopeSpy = sp;
                }}
              />
              <Popover.Root open>
                <EventPopoverContent
                  {...defaultProps}
                  occurrence={originalRecurringEventOccurrence}
                />
              </Popover.Root>
              <RecurringScopeDialog containerRef={containerRef} />
            </EventCalendarProvider>
          </React.Fragment>,
        );

        await user.clear(screen.getByLabelText(/start time/i));
        await user.type(screen.getByLabelText(/start time/i), '10:05');
        await user.clear(screen.getByLabelText(/end time/i));
        await user.type(screen.getByLabelText(/end time/i), '10:35');
        await user.click(screen.getByRole('button', { name: /save changes/i }));

        await screen.findByText(/Apply this change to:/i);
        await user.click(screen.getByText(/All events in the series/i));
        await user.click(screen.getByRole('button', { name: /Cancel/i }));

        expect(updateRecurringEventSpy?.calledOnce).to.equal(true);
        expect(selectRecurringEventUpdateScopeSpy?.called).to.equal(true);
        expect(selectRecurringEventUpdateScopeSpy?.lastCall.firstArg).to.equal(null);
        expect(updateRecurringEventSpy?.callCount).to.equal(1);
      });

      it("should call updateRecurringEvent with scope 'all' and not include rrule if not modified on Submit", async () => {
        let updateRecurringEventSpy, selectRecurringEventUpdateScopeSpy;
        const containerRef = React.createRef<HTMLDivElement>();

        const { user } = render(
          <React.Fragment>
            <div ref={containerRef} />
            <EventCalendarProvider events={[originalRecurringEvent]} resources={resources}>
              <StoreSpy
                Context={EventCalendarStoreContext}
                method="updateRecurringEvent"
                onSpyReady={(sp) => {
                  updateRecurringEventSpy = sp;
                }}
              />
              <StoreSpy
                Context={EventCalendarStoreContext}
                method="selectRecurringEventUpdateScope"
                onSpyReady={(sp) => {
                  selectRecurringEventUpdateScopeSpy = sp;
                }}
              />
              <Popover.Root open>
                <EventPopoverContent
                  {...defaultProps}
                  occurrence={originalRecurringEventOccurrence}
                />
              </Popover.Root>
              <RecurringScopeDialog containerRef={containerRef} />
            </EventCalendarProvider>
          </React.Fragment>,
        );

        await user.clear(screen.getByLabelText(/start time/i));
        await user.type(screen.getByLabelText(/start time/i), '10:05');
        await user.clear(screen.getByLabelText(/end time/i));
        await user.type(screen.getByLabelText(/end time/i), '10:35');
        await user.click(screen.getByRole('button', { name: /save changes/i }));

        await screen.findByText(/Apply this change to:/i);
        await user.click(screen.getByText(/All events in the series/i));
        await user.click(screen.getByRole('button', { name: /Confirm/i }));

        expect(updateRecurringEventSpy?.calledOnce).to.equal(true);
        const openPayload = updateRecurringEventSpy.lastCall.firstArg;

        expect(openPayload.changes.id).to.equal(originalRecurringEvent.id);
        expect(openPayload.changes.title).to.equal('Daily standup');
        expect(openPayload.changes.description).to.equal('sync');
        expect(openPayload.changes.allDay).to.equal(false);
        expect(openPayload.changes.start).to.toEqualDateTime(
          adapter.date('2025-06-11T10:05:00', 'default'),
        );
        expect(openPayload.changes.end).to.toEqualDateTime(
          adapter.date('2025-06-11T10:35:00', 'default'),
        );
        expect(openPayload.changes).to.not.have.property('rrule');

        expect(selectRecurringEventUpdateScopeSpy?.calledOnce).to.equal(true);
        expect(selectRecurringEventUpdateScopeSpy?.lastCall.firstArg).to.equal('all');
      });

      it("should call updateRecurringEvent with scope 'only-this' and include rrule if modified on Submit", async () => {
        let updateRecurringEventSpy, selectRecurringEventUpdateScopeSpy;
        const containerRef = React.createRef<HTMLDivElement>();

        const { user } = render(
          <React.Fragment>
            <div ref={containerRef} />
            <EventCalendarProvider events={[originalRecurringEvent]} resources={resources}>
              <StoreSpy
                Context={EventCalendarStoreContext}
                method="updateRecurringEvent"
                onSpyReady={(sp) => {
                  updateRecurringEventSpy = sp;
                }}
              />
              <StoreSpy
                Context={EventCalendarStoreContext}
                method="selectRecurringEventUpdateScope"
                onSpyReady={(sp) => {
                  selectRecurringEventUpdateScopeSpy = sp;
                }}
              />
              <Popover.Root open>
                <EventPopoverContent
                  {...defaultProps}
                  occurrence={originalRecurringEventOccurrence}
                />
              </Popover.Root>
              <RecurringScopeDialog containerRef={containerRef} />
            </EventCalendarProvider>
          </React.Fragment>,
        );
        // We update the recurrence from daily to weekly
        await user.click(screen.getByRole('tab', { name: /recurrence/i }));
        await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
        await user.click(await screen.findByRole('option', { name: /repeats weekly/i }));
        await user.click(screen.getByRole('button', { name: /save changes/i }));

        await screen.findByText(/Apply this change to:/i);
        await user.click(screen.getByText(/Only this event/i));
        await user.click(screen.getByRole('button', { name: /Confirm/i }));

        expect(updateRecurringEventSpy?.calledOnce).to.equal(true);
        const openPayload = updateRecurringEventSpy.lastCall.firstArg;

        expect(openPayload.changes.id).to.equal(originalRecurringEvent.id);
        expect(openPayload.changes.title).to.equal(originalRecurringEventOccurrence.title);
        expect(openPayload.changes.description).to.equal(
          originalRecurringEventOccurrence.description,
        );
        expect(openPayload.changes.allDay).to.equal(originalRecurringEventOccurrence.allDay);
        expect(openPayload.changes.rrule).to.deep.equal({
          freq: 'WEEKLY',
          interval: 1,
          byDay: ['WE'],
        });
        expect(selectRecurringEventUpdateScopeSpy?.calledOnce).to.equal(true);
        expect(selectRecurringEventUpdateScopeSpy?.lastCall.firstArg).to.equal('only-this');
      });

      it('should call updateRecurringEvent with scope "this-and-following" and send rrule as undefined when "no repeat" is selected on Submit', async () => {
        let updateRecurringEventSpy, selectRecurringEventUpdateScopeSpy;
        const containerRef = React.createRef<HTMLDivElement>();

        const { user } = render(
          <React.Fragment>
            <div ref={containerRef} />
            <EventCalendarProvider events={[originalRecurringEvent]} resources={resources}>
              <StoreSpy
                Context={EventCalendarStoreContext}
                method="updateRecurringEvent"
                onSpyReady={(sp) => {
                  updateRecurringEventSpy = sp;
                }}
              />
              <StoreSpy
                Context={EventCalendarStoreContext}
                method="selectRecurringEventUpdateScope"
                onSpyReady={(sp) => {
                  selectRecurringEventUpdateScopeSpy = sp;
                }}
              />
              <Popover.Root open>
                <EventPopoverContent
                  {...defaultProps}
                  occurrence={originalRecurringEventOccurrence}
                />
              </Popover.Root>
              <RecurringScopeDialog containerRef={containerRef} />
            </EventCalendarProvider>
          </React.Fragment>,
        );

        await user.click(screen.getByRole('tab', { name: /recurrence/i }));
        await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
        await user.click(await screen.findByRole('option', { name: /don.?t repeat/i }));
        await user.click(screen.getByRole('button', { name: /save changes/i }));

        await screen.findByText(/Apply this change to:/i);
        await user.click(screen.getByText(/This and following events/i));
        await user.click(screen.getByRole('button', { name: /Confirm/i }));

        expect(updateRecurringEventSpy?.calledOnce).to.equal(true);
        const openPayload = updateRecurringEventSpy.lastCall.firstArg;

        expect(openPayload.changes.id).to.equal(originalRecurringEvent.id);
        expect(openPayload.changes.rrule).to.equal(undefined);

        expect(selectRecurringEventUpdateScopeSpy?.calledOnce).to.equal(true);
        expect(selectRecurringEventUpdateScopeSpy?.lastCall.firstArg).to.equal(
          'this-and-following',
        );
      });

      describe('Recurrence Custom behavior', () => {
        it('should render recurrence fields as disabled when not recurrent', async () => {
          const { user } = render(
            <EventCalendarProvider events={[DEFAULT_EVENT]} resources={resources}>
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));

          expect(screen.getByRole('combobox', { name: /recurrence/i }).textContent).to.match(
            /don't repeat/i,
          );

          expect(screen.getByLabelText(/repeat/i)).to.have.attribute('aria-disabled', 'true');
          expect(screen.getByText('Ends').parentElement).to.have.attribute('aria-disabled', 'true');
        });

        it('should keep recurrence fields disabled when a preset is selected', async () => {
          const { user } = render(
            <EventCalendarProvider events={[DEFAULT_EVENT]} resources={resources}>
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));
          await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
          await user.click(await screen.findByRole('option', { name: /repeats daily/i }));

          expect(screen.getByLabelText(/repeat/i)).to.have.attribute('aria-disabled', 'true');
          expect(screen.getByText('Never').parentElement).to.have.attribute(
            'aria-disabled',
            'true',
          );
        });

        it('should enable recurrence fields when selecting the custom repeat rule option', async () => {
          const { user } = render(
            <EventCalendarProvider events={[DEFAULT_EVENT]} resources={resources}>
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));
          await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
          await user.click(await screen.findByRole('option', { name: /custom repeat rule/i }));

          expect(screen.getByLabelText(/repeat/i)).not.to.have.attribute('disabled');
          expect(screen.getByText('Never').parentElement).not.to.have.attribute('disabled');
        });

        it('should submit custom recurrence with Ends: after', async () => {
          const onEventsChange = spy();

          const { user } = render(
            <EventCalendarProvider
              events={[DEFAULT_EVENT]}
              resources={resources}
              onEventsChange={onEventsChange}
            >
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));
          await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
          await user.click(await screen.findByRole('option', { name: /custom/i }));

          // Every: set interval = 2
          const repeatGroup = screen.getByRole('group', { name: /repeat/i });
          const intervalInput = within(repeatGroup).getByRole('spinbutton');
          await user.click(intervalInput);
          await user.keyboard('{Control>}a{/Control}2');

          // Frequency: weeks
          const freqCombo = within(repeatGroup).getByRole('combobox');
          await user.click(freqCombo);
          await user.click(await screen.findByRole('option', { name: /weeks/i }));

          // Ends: select "After"
          const endsFieldset = screen.getByRole('group', { name: /ends/i });
          const afterRadio = within(endsFieldset).getByText('After');
          await user.click(afterRadio);

          // Set count = 5
          const countInput = within(endsFieldset).getByRole('spinbutton');
          await user.click(countInput);
          await user.keyboard('{Control>}a{/Control}5');

          await user.click(screen.getByRole('button', { name: /save changes/i }));

          expect(onEventsChange.calledOnce).to.equal(true);
          const updated = onEventsChange.firstCall.firstArg[0];

          expect(updated.rrule).to.deep.equal({
            freq: 'WEEKLY',
            byDay: [],
            byMonthDay: [],
            interval: 2,
            count: 5,
            until: undefined,
          });
        });

        it('should submit custom recurrence with Ends: never', async () => {
          const onEventsChange = spy();

          const { user } = render(
            <EventCalendarProvider
              events={[DEFAULT_EVENT]}
              resources={resources}
              onEventsChange={onEventsChange}
            >
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));
          await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
          await user.click(await screen.findByRole('option', { name: /custom/i }));

          // Every: set interval = 2, frequency = months
          const repeatGroup = screen.getByRole('group', { name: /repeat/i });
          const intervalInput = within(repeatGroup).getByRole('spinbutton');
          await user.click(intervalInput);
          await user.keyboard('{Control>}a{/Control}2');

          const freqCombo = within(repeatGroup).getByRole('combobox');
          await user.click(freqCombo);
          await user.click(await screen.findByRole('option', { name: /months/i }));

          // Ends: keep Never (default)
          const endsFieldset = screen.getByRole('group', { name: /ends/i });
          expect(within(endsFieldset).getByText('Never').parentElement).to.have.attribute(
            'aria-checked',
            'true',
          );

          await user.click(screen.getByRole('button', { name: /save changes/i }));

          expect(onEventsChange.calledOnce).to.equal(true);
          const updated = onEventsChange.firstCall.firstArg[0];

          expect(updated.rrule).to.deep.equal({
            freq: 'MONTHLY',
            byDay: [],
            byMonthDay: [],
            interval: 2,
          });
        });

        it('should submit custom recurrence with Ends: until and selected date', async () => {
          const onEventsChange = spy();

          const { user } = render(
            <EventCalendarProvider
              events={[DEFAULT_EVENT]}
              resources={resources}
              onEventsChange={onEventsChange}
            >
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));
          await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
          await user.click(await screen.findByRole('option', { name: /custom/i }));

          // Every: set interval = 3, frequency = years
          const repeatGroup = screen.getByRole('group', { name: /repeat/i });
          const intervalInput = within(repeatGroup).getByRole('spinbutton');
          await user.click(intervalInput);
          await user.keyboard('{Control>}a{/Control}3');

          const freqCombo = within(repeatGroup).getByRole('combobox');
          await user.click(freqCombo);
          await user.click(await screen.findByRole('option', { name: /years/i }));

          // Ends: "Until" and date 2025-07-20
          const untilRadio = within(screen.getByRole('group', { name: /ends/i })).getByText(
            'Until',
          );
          await user.click(untilRadio);
          const labelEl = untilRadio.closest('label');
          const dateInput = labelEl?.querySelector('input[type="date"]') as HTMLInputElement;
          await user.click(dateInput);
          await user.clear(dateInput);
          await user.type(dateInput, '2025-07-20');

          await user.click(screen.getByRole('button', { name: /save changes/i }));

          expect(onEventsChange.calledOnce).to.equal(true);
          const updated = onEventsChange.firstCall.firstArg[0];

          expect(updated.rrule).to.deep.include({ freq: 'YEARLY', interval: 3 });
          expect(updated.rrule?.count ?? undefined).to.equal(undefined);
          expect(updated.rrule?.until).toEqualDateTime('2025-07-20T00:00:00.000Z');
        });

        it('should submit custom weekly with selected weekdays', async () => {
          const onEventsChange = spy();

          const { user } = render(
            <EventCalendarProvider
              events={[DEFAULT_EVENT]}
              resources={resources}
              onEventsChange={onEventsChange}
            >
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));
          await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
          await user.click(await screen.findByRole('option', { name: /custom/i }));

          const repeatGroup = screen.getByRole('group', { name: /repeat/i });
          const freqCombo = within(repeatGroup).getByRole('combobox');
          await user.click(freqCombo);
          await user.click(await screen.findByRole('option', { name: /weeks/i }));

          // Select Monday and Friday in the weekly day toggles
          await user.click(screen.getByRole('button', { name: /monday/i }));
          await user.click(screen.getByRole('button', { name: /friday/i }));

          await user.click(screen.getByRole('button', { name: /save changes/i }));

          expect(onEventsChange.calledOnce).to.equal(true);
          const updated = onEventsChange.firstCall.firstArg[0];

          expect(updated.rrule).to.deep.equal({
            freq: 'WEEKLY',
            interval: 1,
            byDay: ['MO', 'FR'],
            byMonthDay: [],
          });
        });

        it('should submit custom monthly with "day of month" option', async () => {
          const onEventsChange = spy();

          const { user } = render(
            <EventCalendarProvider
              events={[DEFAULT_EVENT]}
              resources={resources}
              onEventsChange={onEventsChange}
            >
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));
          await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
          await user.click(await screen.findByRole('option', { name: /custom/i }));

          const repeatGroup = screen.getByRole('group', { name: /repeat/i });
          const freqCombo = within(repeatGroup).getByRole('combobox');
          await user.click(freqCombo);
          await user.click(await screen.findByRole('option', { name: /months/i }));

          await user.click(screen.getByRole('button', { name: /day 26/i })); // DEFAULT_EVENT is 2025-05-26

          await user.click(screen.getByRole('button', { name: /save changes/i }));

          expect(onEventsChange.calledOnce).to.equal(true);
          const updated = onEventsChange.firstCall.firstArg[0];

          expect(updated.rrule).to.deep.equal({
            freq: 'MONTHLY',
            interval: 1,
            byMonthDay: [26],
          });
        });

        it('should submit custom monthly with "ordinal weekday" option', async () => {
          const onEventsChange = spy();

          const { user } = render(
            <EventCalendarProvider
              events={[DEFAULT_EVENT]}
              resources={resources}
              onEventsChange={onEventsChange}
            >
              <Popover.Root open>
                <EventPopoverContent {...defaultProps} />
              </Popover.Root>
            </EventCalendarProvider>,
          );

          await user.click(screen.getByRole('tab', { name: /recurrence/i }));
          await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
          await user.click(await screen.findByRole('option', { name: /custom/i }));

          const repeatGroup = screen.getByRole('group', { name: /repeat/i });
          const freqCombo = within(repeatGroup).getByRole('combobox');
          await user.click(freqCombo);
          await user.click(await screen.findByRole('option', { name: /months/i }));

          // The DEFAULT_EVENT (2025-05-26 Mon) is the last Monday of the month ("-1MO")
          await user.click(screen.getByRole('button', { name: /mon.*last week/i }));

          await user.click(screen.getByRole('button', { name: /save changes/i }));

          expect(onEventsChange.calledOnce).to.equal(true);
          const updated = onEventsChange.firstCall.firstArg[0];

          expect(updated.rrule).to.deep.equal({
            freq: 'MONTHLY',
            interval: 1,
            byDay: ['-1MO'],
          });
        });
      });
    });

    describe('Non-recurring events', () => {
      const nonRecurringEvent: SchedulerEvent = EventBuilder.new()
        .id('non-recurring-1')
        .title('Task')
        .description('description')
        .singleDay('2025-06-12T14:00:00')
        .build();
      const nonRecurringEventOccurrence = EventBuilder.new(adapter)
        .id(nonRecurringEvent.id)
        .title(nonRecurringEvent.title)
        .description(nonRecurringEvent.description)
        .singleDay('2025-06-12T14:00:00')
        .toOccurrence();

      it('should call updateEvent with updated values on Submit', async () => {
        let updateEventSpy;

        const { user } = render(
          <EventCalendarProvider events={[nonRecurringEvent]} resources={resources}>
            <StoreSpy
              Context={EventCalendarStoreContext}
              method="updateEvent"
              onSpyReady={(sp) => {
                updateEventSpy = sp;
              }}
            />
            <Popover.Root open>
              <EventPopoverContent {...defaultProps} occurrence={nonRecurringEventOccurrence} />
            </Popover.Root>
          </EventCalendarProvider>,
        );
        await user.type(screen.getByLabelText(/event title/i), ' updated ');
        await user.clear(screen.getByLabelText(/description/i));
        await user.type(screen.getByLabelText(/description/i), '  new description  ');
        await user.click(screen.getByRole('button', { name: /resource/i }));
        await user.click(await screen.findByRole('menuitemradio', { name: /work/i }));
        await user.click(screen.getByRole('button', { name: /save changes/i }));

        expect(updateEventSpy?.calledOnce).to.equal(true);
        const payload = updateEventSpy.lastCall.firstArg;

        expect(payload.id).to.equal(nonRecurringEvent.id);
        expect(payload.title).to.equal('Task updated');
        expect(payload.description).to.equal('new description');
        expect(payload.resource).to.equal('r1');
        expect(payload.allDay).to.equal(false);
        expect(payload.start).toEqualDateTime(adapter.date('2025-06-12T14:00:00', 'default'));
        expect(payload.end).toEqualDateTime(adapter.date('2025-06-12T15:00:00', 'default'));
        expect(payload.rrule).to.equal(undefined);
      });

      it('should call updateEvent with updated values and send rrule if recurrence was selected on Submit', async () => {
        let updateEventSpy;

        const { user } = render(
          <EventCalendarProvider events={[nonRecurringEvent]} resources={resources}>
            <StoreSpy
              Context={EventCalendarStoreContext}
              method="updateEvent"
              onSpyReady={(sp) => {
                updateEventSpy = sp;
              }}
            />
            <Popover.Root open>
              <EventPopoverContent {...defaultProps} occurrence={nonRecurringEventOccurrence} />
            </Popover.Root>
          </EventCalendarProvider>,
        );
        await user.click(screen.getByRole('tab', { name: /recurrence/i }));
        await user.click(screen.getByRole('combobox', { name: /recurrence/i }));
        await user.click(await screen.findByRole('option', { name: /repeats daily/i }));
        await user.click(screen.getByRole('button', { name: /save changes/i }));

        expect(updateEventSpy?.calledOnce).to.equal(true);
        const payload = updateEventSpy.lastCall.firstArg;

        expect(payload.id).to.equal(nonRecurringEvent.id);
        expect(payload.rrule).to.deep.equal({
          freq: 'DAILY',
          interval: 1,
        });
      });
    });
  });
});
